/*	--- RMI远程调用 --- 

Java的RMI远程调用是指，一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。

RMI 是 Remote Method Invocation的缩写。

【提供服务的一方】我们称之为【服务器】，而实现远程调用的一方我们称之为客户端。

我们先来实现一个最简单的RMI：
【服务器】会提供一个【WorldClock服务】，允许【客户端】获取指定时区的时间，即允许【客户端】调用下面的方法：*/
LocalDateTime getLocalDateTime(Sting zoneId);


/* 要实现RMI，【服务器】和【客户端】必须共享【同一个接口】。

我们定义一个WorldClock接口，代码如下：*/
public interface WorldClock extends Remote {
	LocalDateTime
}



/*
【Java的RMI】规定此接口必须派生自【java.rmi.Remote】，并在每个方法声明抛出【RemoteException】。

下一步是编写【服务器的实现类】，因为【客户端请求的调用方法getLocalDateTime()】最终会通过【服务器实现类】返回结果。

实现类WorldClockService代码如下：*/
public class WorldClockService implements WorldClock {
	@Override
	public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
		return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
	}
}


/*
现在，【服务器端的服务相关代码】就编写完毕。

我们需要通过【Java RMI】提供的【一系列底层支持接口】，把上面编写的【服务】以【RMI的形式】暴露在网络上，【客户端】才能调用：*/
public class Server {
	public static void main(String[] args) throws RemoteException {
		System.out.println("create World clock remote service...");

		// 实例化一个WorldClock(向上转型):
		WorldClock worldClock = new WorldClockService();

	//核心
		// 将【此服务】转换为【远程服务的接口】, Unicast 单点广播
		WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);

		// 将【RMI服务】注册到【1099 端口】
		Registry registry = LocateRegistry.createRegistry(1099);
			// LocateRegistry.createRegistry(Port);
			// Registry n. 域名注册局,注册表项
			// Locate v. 安置,创办...于

		// 注册此服务,服务名为"WorldClock"
		registry.rebind("WorldClock", skeleton);
			// bind v. 捆绑,结合
	}
}


/*
上述代码主要目的是通过RMI提供的相关类，将我们自己的WorldClock实例注册到RMI服务上。RMI的默认端口是1099，最后一步注册服务时通过rebind()指定服务名称为"WorldClock"。


---------------------------------

下一步我们就可以编写【客户端】代码。

【RMI】要求【服务器】和【客户端】共享同一个接口，因此我们要把【WorldClock.java】这个【接口文件】复制到【客户端】，然后在【客户端】实现【RMI调用】：*/
public class Client{
	public static void main(String[] args) throws RemoteException, NotBoundException {

		// 连接到服务器localhost, 端口1099
		Registry registry = LocateRegistry.getRegistry("localhost", 1099);
			// LocateRegistry.getRegistry(IP, Port);

		// 查找名为"WorldClock"的服务,并强制转型为【WorldClock接口】
		WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");

		// 正常调用接口的方法
		LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");

		// 打印调用结果
		System.out.println(now);
	}
}



/*
先运行【Server 服务器】，再运行【Client 客户端】。

从运行结果可知，因为客户端只有接口，并没有实现类，因此，客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。

整个过程实际上非常简单，对客户端来说，【客户端】持有的【WorldClock接口】实际上对应了一个“实现类”，它是由Registry内部动态生成的，并负责把方法调用通过网络传递到服务器端。

而【服务器】端接收网络调用的服务并不是我们自己编写的WorldClockService，而是Registry自动生成的代码。

我们把【客户端】的“实现类”称为【stub, n. 存根,树桩】，而【服务器端的网络服务类】称为【skeleton,骨架】，【客户端的stub实现类】会真正调用【服务器端的WorldClockService】，获取结果，然后把结果通过网络传递给客户端。

整个过程由【RMI底层】负责实现【序列化和反序列化】：

┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐         ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
  ┌─────────────┐                                 ┌─────────────┐
│ │   Service   │ │         │                     │   Service   │ │
  └─────────────┘                                 └─────────────┘
│        ▲        │         │                            ▲        │
         │                                               │
│        │        │         │                            │        │
  ┌─────────────┐   Network   ┌───────────────┐   ┌─────────────┐
│ │ Client Stub ├─┼─────────┼>│Server Skeleton│──>│Service Impl │ │
  └─────────────┘             └───────────────┘   └─────────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ┘         └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘



【Java的RMI】严重依赖序列化和反序列化，而这种情况下可能会造成严重的安全漏洞，因为】Java的序列化和反序列化、不但涉及到数据，还涉及到二进制的字节码，即使使用白名单机制也很难保证100%排除恶意构造的字节码。

因此，使用RMI时，双方必须是内网互相信任的机器，不要把【1099端口】暴露在【公网上】作为对外服务。


此外，Java的RMI调用机制决定了【双方】必须是【Java程序】，其他语言很难调用Java的RMI。

如果要使用【不同语言】进行【RPC, Remote Procedure Call】调用，可以选择更通用的协议，例如gRPC。
(gRPC, https://grpc.io/)


==============================================================*/






/*	----- RMI远程调用 -----

1. Java提供了RMI实现远程方法调用：

2. RMI 通过自动生成【stub】和【skeleton】实现【网络调用】，【客户端】只需要【查找服务】并【获得接口实例】，【服务器端】只需要【编写实现类,并注册为服务】;

3. RMI的序列化和反序列化可能会造成安全漏洞，因此调用双方必须是内网互相信任的机器，不要把1099端口暴露在公网上作为对外服务。


----------------------------

Server:
	WorldClock worldClock = new WorldClockService();
	
	// 将【此服务】转换为【远程服务的接口】, Unicast 单点广播
	WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);

	// 将【RMI服务】注册到【1099 端口】
	Registry registry = LocateRegistry.createRegistry(1099);

	// 注册此服务,服务名为"WorldClock"
	registry.rebind("WorldClock", skeleton);



Client: 
	// 连接到服务器的IP和Port
	Registry registry = LocateRegistry.getRegistry(IP, Port);
		
	// 寻找"服务"
	WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");

	// 调用接口的方法
	LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");

	// 打印调用结果
	System.out.println(now);







*/


